/*******************************************************************************
 * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    emil.crumhorn@gmail.com - initial API and implementation
 *******************************************************************************/ 

package com.hexapixel.widgets.collapsiblebuttons;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;

import com.hexapixel.widgets.generic.ImageCache;

public class ToolbarComposite extends Composite implements MouseListener, MouseMoveListener, MouseTrackListener {

    private List<TBItem> mToolBarItems;
    private TBItem mLastHover;
    private CustomButton mSelectedItem;
    private CollapsibleButtons mButtonComposite;

    private boolean mEnableDoubleBuffering = true;
    private boolean mCreated = false;

    private static Image mOutlook2005ArrowsImage = ImageCache.getImage("arrows.gif");
    private static Image mOutlook2007ArrowImage = ImageCache.getImage("o2007arrow.gif");
    private Rectangle mArrowsBounds;
    private boolean mArrowHover = false;
    
    private IColorManager mColorManager;
	private AbstractButtonPainter mButtonPainter;
	
	private Image mArrowImage;
	private ISettings mSettings;

	/**
	 * Creates a new toolbar composite.
	 * 
	 * @param bc ButtonComposite parent
	 * @param style Composite style
	 */
    public ToolbarComposite(CollapsibleButtons bc, int style) {
        super(bc, style | SWT.NO_BACKGROUND);

        this.mButtonPainter = new AbstractButtonPainter();
        this.mButtonComposite = bc;
        this.mColorManager = bc.getColorManager();
        this.mSettings = bc.getSettings();
        
        if (mColorManager.getTheme() == IColorManager.SKIN_OFFICE_2007) 
        	mArrowImage = mOutlook2007ArrowImage;
        else 
        	mArrowImage = mOutlook2005ArrowsImage;
        
        mToolBarItems = new ArrayList<TBItem>();

        addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent event) {
                repaint(event);
            }
        });

        addMouseListener(this);
        addMouseTrackListener(this);
        addMouseMoveListener(this);
    }

    private void repaint(PaintEvent event) {
        GC gc = event.gc;
        if (mCreated && mEnableDoubleBuffering) {
            try {
                Image buffer = new Image(Display.getDefault(), super.getBounds());
                GC gc2 = new GC(buffer);
                drawOntoGC(gc2);

                // transfer the image buffer onto this canvas
                // just drawImage(buffer, w, h) didn't work, so we do the whole source transfer call
                Rectangle b = getBounds();
                gc.drawImage(buffer, 0, 0, b.width, b.height, 0, 0, b.width, b.height);

                // dispose the buffer, very important or we'll run out of address space for buffered images
                buffer.dispose();
                gc2.dispose();
            }
            catch (IllegalArgumentException iea) {
                // seems to come here for some reason when we switch phases while the gantt chart is being viewed, I'm not sure why
                // but no time to figure it out for the demo.. so instead of buffering, just draw it onto the GC
                drawOntoGC(gc);
            }
        } else {
            drawOntoGC(gc);
            mCreated = true;
        }

        gc.dispose();
    }

    private void drawOntoGC(GC gc) {
        Rectangle rect = getClientArea();
        Rectangle imageBounds = mArrowImage.getBounds();
        int verticalLoc = (getBounds().height/2) - (mArrowImage == null ? 0 : mArrowImage.getBounds().height/2);
        // move it down just a smidge, the human eye percieves things mis-aligned when exactly centered
        verticalLoc += 1;
    	mButtonPainter.paintBackground(gc, mColorManager, mSettings, rect, false, false);

        int right = rect.width;
        
        gc.drawImage(mArrowImage, rect.width - imageBounds.width, verticalLoc);        
        mArrowsBounds = new Rectangle(rect.width - imageBounds.width, verticalLoc, imageBounds.width, imageBounds.height);
        right -= imageBounds.width + mSettings.getToolBarSpacing();

        // reorganize items if stuff have been hidden/shown (permanently)
        orderItems();

        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem tb = (TBItem) mToolBarItems.get(i);
            if (tb.getHidden())
            	continue;

            if (tb.getButton() == mSelectedItem) {
            	Rectangle cur = tb.getBounds();
            	// TODO: Clean up code-repeat
            	if (cur == null) {
            		if (tb.getButton().getToolBarImage() != null) {
	                	Rectangle imBounds = tb.getButton().getToolBarImage().getBounds();
	            		cur = new Rectangle(right - imBounds.width, verticalLoc, imBounds.width, imBounds.width);
	            		tb.setBounds(cur);
            		}
            		else {
            			// basically a non-existant button, but no image = no button, so that's fine
            			tb.setBounds(new Rectangle(right, verticalLoc, 0, 0));
            		}
            	}
            		
            	Rectangle bounds = new Rectangle(cur.x - mSettings.getToolBarLeftSpacer(), cur.y, cur.width+mSettings.getToolBarRightSpacer(), cur.height);
            	mButtonPainter.paintBackground(gc, mColorManager, mSettings, bounds, false, true);
            }

            Rectangle imBounds = null;
            if (tb.getButton().getToolBarImage() != null) {
            	imBounds = tb.getButton().getToolBarImage().getBounds();
                gc.drawImage(tb.getButton().getToolBarImage(), right - imBounds.width, verticalLoc);
                tb.setBounds(new Rectangle(right - imBounds.width, verticalLoc, imBounds.width, imBounds.width));
            }
            
            right -= (imBounds == null ? 0 : imageBounds.width) + mSettings.getToolBarSpacing();
        }
    }
    
    private void orderItems() {
    	if (mToolBarItems.size() == 0)
    		return;
 
        Collections.sort(mToolBarItems);
    }
        
    @Override
    public Point getSize() {
    	checkWidget();
        return new Point(super.getSize().x, CustomButton.BUTTON_HEIGHT);
    }

    public void addItem(CustomButton button) {
    	checkWidget();
        mToolBarItems.add(new TBItem(button));
    }

    public void removeItem(CustomButton button) {
    	checkWidget();
        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem item = (TBItem) mToolBarItems.get(i);
            if (item.getButton() == button) {
                mToolBarItems.remove(item);
                //redraw();
                break;
            }
        }
    }
    
    public void hideButton(CustomButton button) {
    	checkWidget();
        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem item = (TBItem) mToolBarItems.get(i);
            if (item.getButton() == button) {
                item.setHidden(true);
                break;
            }
        }    	
    }

    public void setSelectedItem(CustomButton button) {
    	checkWidget();
        clearHover();
        clearArrowsHover();
        clearSelection();
        mSelectedItem = button;

        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem item = (TBItem) mToolBarItems.get(i);

            if (item.getButton() == mSelectedItem) {
                Rectangle lb = item.getBounds();
                GC gc = new GC(this);
                Rectangle rect = new Rectangle(lb.x - mSettings.getToolBarLeftSpacer(), 0, lb.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT);
                mButtonPainter.paintBackground(gc, mColorManager, mSettings, rect, false, true);
                gc.drawImage(item.getButton().getToolBarImage(), lb.x, lb.y);
                gc.dispose();
            }
        }

    }

    public void mouseDoubleClick(MouseEvent event) {
    	checkWidget();
    }

    public void mouseDown(MouseEvent event) {
    	checkWidget();
        Rectangle bigArrowsBounds = new Rectangle(mArrowsBounds.x, 0, mArrowsBounds.width, CustomButton.BUTTON_HEIGHT);
        if (isInside(event.x, event.y, bigArrowsBounds)) {
            GC gc = new GC(this);
            Rectangle rect = new Rectangle(mArrowsBounds.x - mSettings.getToolBarLeftSpacer(), 0, mArrowsBounds.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT);
            mButtonPainter.paintBackground(gc, mColorManager, mSettings, rect, false, true);
            
            gc.drawImage(mArrowImage, mArrowsBounds.x, mArrowsBounds.y);        
            	
            gc.dispose();

            MenuManager mm = new MenuManager();
            Action menuShowMoreButtons = new Action() {
            	public void run() {
            		mButtonComposite.showNextButton();
            	}
            };
            Action menuShowFewerButtons = new Action() {
            	public void run() {
            		mButtonComposite.hideNextButton();
            	}
            };
            menuShowMoreButtons.setText("Show More Buttons");
            menuShowFewerButtons.setText("Show Fewer Buttons");
            
            mm.add(menuShowMoreButtons);
            mm.add(menuShowFewerButtons);
            mm.add(new Separator());
            MenuManager more = new MenuManager("Add or Remove Buttons");
            mm.add(more);
            
            List<CustomButton> cbs = mButtonComposite.getItems();            
            for (int i = 0; i < cbs.size(); i++) {            	
            	CustomButton cb = cbs.get(i);
            	more.add(new ShowHideAction(cb, cb.getText(), cb.getToolBarImage()));
            }
           
            Menu menu = mm.createContextMenu(this);
            menu.setVisible(true);
            return;
        }

        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem item = (TBItem) mToolBarItems.get(i);
            if (item.getBounds() != null) {
                if (isInside(event.x, event.y, item.getBounds())) {
                    mButtonComposite.selectItemAndLoad(item.getButton());
                    break;
                }
            }
        }
    }

    public void mouseUp(MouseEvent event) {
    	checkWidget();
    }

    public void mouseMove(MouseEvent event) {
    	checkWidget();
        TBItem found = null;

        Rectangle bigArrowsBounds = new Rectangle(mArrowsBounds.x, 0, mArrowsBounds.width, CustomButton.BUTTON_HEIGHT);
        if (isInside(event.x, event.y, bigArrowsBounds)) {
            setToolTipText(null);
            GC gc = new GC(this);
        	Rectangle rect = new Rectangle(mArrowsBounds.x - mSettings.getToolBarLeftSpacer(), 0, mArrowsBounds.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT);
        	mButtonPainter.paintBackground(gc, mColorManager, mSettings, rect, true, false);

        	gc.drawImage(mArrowImage, mArrowsBounds.x, mArrowsBounds.y);        
            gc.dispose();
            mArrowHover = true;
            return;
        }

        clearArrowsHover();

        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem item = (TBItem) mToolBarItems.get(i);
            if (item.getBounds() != null) {
                if (isInside(event.x, event.y, item.getBounds())) {
                    found = item;
                    break;
                }
            }
        }

        if (found == null) {
            clearHover();
            return;
        }

        setToolTipText(found.getButton().getToolTip());

        if (found.isHovered()) {
            return;
        }

        if (found.getButton() == mSelectedItem) {
            return;
        }

        clearHover();

        GC gc = new GC(this);
        Rectangle tbBounds = found.getBounds();
        Rectangle toUse = new Rectangle(tbBounds.x - mSettings.getToolBarLeftSpacer(), 0, tbBounds.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT);
        mButtonPainter.paintBackground(gc, mColorManager, mSettings, toUse, true, false);
        gc.drawImage(found.getButton().getToolBarImage(), tbBounds.x, tbBounds.y);
        gc.dispose();
        found.setHovered(true);
        mLastHover = found;
    }

    public void mouseEnter(MouseEvent event) {

    }

    public void mouseExit(MouseEvent event) {
    	checkWidget();
        clearHover();
        clearArrowsHover();
    }

    public void mouseHover(MouseEvent event) {

    }

    private void clearSelection() {
        for (int i = 0; i < mToolBarItems.size(); i++) {
            TBItem item = (TBItem) mToolBarItems.get(i);
            if (item.getButton() == mSelectedItem) {
                GC gc = new GC(this);
                Rectangle lb = item.getBounds();
                redraw(lb.x - mSettings.getToolBarLeftSpacer(), 0, lb.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT, false);
                gc.drawImage(item.getButton().getToolBarImage(), lb.x, lb.y);
                gc.dispose();
            }
        }
    }

    private void clearHover() {
        if (mLastHover != null) {
            GC gc = new GC(this);
            Rectangle lb = mLastHover.getBounds();
            redraw(lb.x - mSettings.getToolBarLeftSpacer(), 0, lb.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT, false);
            gc.drawImage(mLastHover.getButton().getToolBarImage(), lb.x, lb.y);
            gc.dispose();
            mLastHover.setHovered(false);
            mLastHover = null;
            setToolTipText(null);
        }
    }

    private void clearArrowsHover() {
        if (mArrowHover) {
            GC gc = new GC(this);
            redraw(mArrowsBounds.x - mSettings.getToolBarLeftSpacer(), 0, mArrowsBounds.width + mSettings.getToolBarRightSpacer(), CustomButton.BUTTON_HEIGHT, false);
        	gc.drawImage(mArrowImage, mArrowsBounds.x, mArrowsBounds.y);        
            gc.dispose();
            mArrowHover = false;
        }
    }

    private boolean isInside(int x, int y, Rectangle rect) {
        if (rect == null) {
            return false;
        }

        if (x >= rect.x && y >= rect.y && x <= (rect.x + rect.width) && y <= (rect.y + rect.height)) {
            return true;
        }

        return false;
    }

    class TBItem implements Comparable<TBItem> {
		private Rectangle bounds;
        private CustomButton button;
        private boolean hovered;
        private boolean hidden;

        public TBItem(CustomButton button) {
            this.button = button;
        }

        public Rectangle getBounds() {
            return bounds;
        }

        public void setBounds(Rectangle bounds) {
            this.bounds = bounds;
        }

        public CustomButton getButton() {
            return button;
        }

        public boolean isHovered() {
            return hovered;
        }

        public void setHovered(boolean hovered) {
            this.hovered = hovered;
        }
        
        public void setHidden(boolean hidden) {
        	this.hidden = hidden;
        }
        
        public boolean getHidden() {
        	return this.hidden;
        }
        
        public String toString() {
        	return "[TBItem " + button.getNumber() + "]";
        }

		public int compareTo(TBItem item) {
			Integer one = item.getButton().getNumber();
			Integer two = getButton().getNumber();
			return one.compareTo(two);
		}        
    }

    class ShowHideAction extends Action {
        private CustomButton button;

        public ShowHideAction(CustomButton button, String name, Image image) {
            this.button = button;
            setText(name);
            setImageDescriptor(ImageDescriptor.createFromImage(image));
            setChecked(mButtonComposite.isVisible(button));
        }

        public void run() {
            if (mButtonComposite.isVisible(button)) {
                mButtonComposite.permanentlyHideButton(button);
                setChecked(false);
            } else {
                mButtonComposite.permanentlyShowButton(button);
                setChecked(true);
            }
        }
    }
}

